/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * http://www.gnu.org/copyleft/gpl.html
 */
package l1j.server.server.model;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import l1j.opqlo.NewSystem.Attack.L1AttackModel;
import l1j.opqlo.NewSystem.Attack.L1AttackNpc;
import l1j.opqlo.Server.L1World.L1World;
import l1j.server.server.ActionCodes;
import l1j.server.server.IdFactory;
import l1j.server.server.datatables.MobSkillTable;
import l1j.server.server.datatables.NpcTable;
import l1j.server.server.datatables.SkillsTable;
import l1j.server.server.model.Instance.L1ItemInstance;
import l1j.server.server.model.Instance.L1MonsterInstance;
import l1j.server.server.model.Instance.L1NpcInstance;
import l1j.server.server.model.Instance.L1PcInstance;
import l1j.server.server.model.Instance.L1PetInstance;
import l1j.server.server.model.Instance.L1SummonInstance;
import l1j.server.server.model.skill.L1SkillUse;
import l1j.server.server.serverpackets.S_DoActionGFX;
import l1j.server.server.serverpackets.S_NPCPack;
import l1j.server.server.serverpackets.S_SkillSound;
import l1j.server.server.templates.L1MobSkill;
import l1j.server.server.templates.L1Npc;
import l1j.server.server.templates.L1Skills;
import l1j.server.server.utils.Random;

public class L1MobSkillUse {
	private static Logger _log = Logger.getLogger(L1MobSkillUse.class.getName());

	private L1MobSkill _mobSkillTemplate = null;

	private L1NpcInstance _attacker = null;

	private L1Character _target = null;


	private int _sleepTime = 0;

	private int _skillUseCount[];

	public L1MobSkillUse(L1NpcInstance npc) {
		this._sleepTime = 0;

		this._mobSkillTemplate = MobSkillTable.getInstance().getTemplate(npc.getNpcTemplate().get_npcId());
		if (this._mobSkillTemplate == null) {
			return;
		}
		this._attacker = npc;
		this._skillUseCount = new int[this.getMobSkillTemplate().getSkillSize()];
	}

	// 現在ChangeTargetで有効な値は2,3のみ
	private L1Character changeTarget(final int type,final  int idx) {
		L1Character target;

		switch (type) {
		case L1MobSkill.CHANGE_TARGET_ME:
			target = this._attacker;
			break;
		case L1MobSkill.CHANGE_TARGET_RANDOM:
			// ターゲット候補の選定
			final List<L1Character> targetList = new ArrayList<L1Character>();
			for (final L1Object obj : L1World.getInstance().getVisibleObjects(
					this._attacker)) {
				if (obj instanceof L1PcInstance || obj instanceof L1PetInstance
						|| obj instanceof L1SummonInstance) {
					final L1Character cha = (L1Character) obj;

					final int distance = this._attacker.getLocation().getTileLineDistance(
							cha.getLocation());

					// 発動範囲外のキャラクターは対象外
					if (!this.getMobSkillTemplate().isTriggerDistance(idx, distance)) {
						continue;
					}

					// 障害物がある場合は対象外
					if (!this._attacker.glanceCheck(cha.getX(), cha.getY())) {
						continue;
					}

					if (!this._attacker.getHateList().containsKey(cha)) { // ヘイトがない場合対象外
						continue;
					}

					if (cha.isDead()) { // 死んでるキャラクターは対象外
						continue;
					}

					// ゴースト状態は対象外
					if (cha instanceof L1PcInstance) {
						if (((L1PcInstance) cha).isGhost()) {
							continue;
						}
					}
					targetList.add((L1Character) obj);
				}
			}

			if (targetList.size() == 0) {
				target = this._target;
			} else {
				final int randomSize = targetList.size() * 100;
				final int targetIndex = Random.nextInt(randomSize) / 100;
				target = targetList.get(targetIndex);
			}
			break;

		default:
			target = this._target;
			break;
		}
		return target;
	}

	public L1MobSkill getMobSkillTemplate() {
		return this._mobSkillTemplate;
	}

	private int getSkillUseCount(final int idx) {
		return this._skillUseCount[idx];
	}

	public int getSleepTime() {
		return this._sleepTime;
	}

	/*
	 * トリガーの条件のみチェック。
	 */
	public boolean isSkillTrigger(final L1Character tg) {
		if (this._mobSkillTemplate == null) {
			return false;
		}
		this._target = tg;

		int type;
		type = this.getMobSkillTemplate().getType(0);

		if (type == L1MobSkill.TYPE_NONE) {
			return false;
		}

		int i = 0;
		for (i = 0; i < this.getMobSkillTemplate().getSkillSize()
		&& this.getMobSkillTemplate().getType(i) != L1MobSkill.TYPE_NONE; i++) {

			// changeTargetが設定されている場合、ターゲットの入れ替え
			final int changeType = this.getMobSkillTemplate().getChangeTarget(i);
			if (changeType > 0) {
				this._target = this.changeTarget(changeType, i);
			} else {
				// 設定されてない場合は本来のターゲットにする
				this._target = tg;
			}

			if (this.isSkillUseble(i, false)) {
				return true;
			}
		}
		return false;
	}

	/*
	 * トリガーの条件のみチェック
	 */
	private boolean isSkillUseble(final int skillIdx,final  boolean isTriRnd) {
		boolean useble = false;
		//final int type = getMobSkillTemplate().getType(skillIdx);

		/*if (isTriRnd || type == L1MobSkill.TYPE_SUMMON
				|| type == L1MobSkill.TYPE_POLY) {*/

		//}

		if (this.getMobSkillTemplate().getTriggerHp(skillIdx) > 0) {
			final int hpRatio = this._attacker.getCurrentHp() * 100
			/ this._attacker.getMaxHp();
			if (hpRatio <= this.getMobSkillTemplate().getTriggerHp(skillIdx)) {
				useble = true;
			} else {
				return false;
			}
		}

		if (this.getMobSkillTemplate().getTriggerCompanionHp(skillIdx) > 0) {
			final L1NpcInstance companionNpc = this.searchMinCompanionHp();
			if (companionNpc == null) {
				return false;
			}

			final int hpRatio = companionNpc.getCurrentHp() * 100/ companionNpc.getMaxHp();
			if (hpRatio <= this.getMobSkillTemplate().getTriggerCompanionHp(skillIdx)) {
				useble = true;
				this._target = companionNpc; // ターゲットの入れ替え
			} else {
				return false;
			}
		}

		if (this.getMobSkillTemplate().getTriggerRange(skillIdx) != 0) {
			final int distance = this._attacker.getLocation().getTileLineDistance(this._target.getLocation());

			if (this.getMobSkillTemplate().isTriggerDistance(skillIdx, distance)) {
				useble = true;
			} else {
				return false;
			}
		}

		if (this.getMobSkillTemplate().getTriggerCount(skillIdx) > 0) {
			if (this.getSkillUseCount(skillIdx) < this.getMobSkillTemplate().getTriggerCount(skillIdx)) {
				useble = true;
			} else {
				return false;
			}
		}
		if (this.getMobSkillTemplate().getTriggerRandom(skillIdx) > 0) {
			final 	int chance = Random.nextInt(100) + 1;
			if (chance < this.getMobSkillTemplate().getTriggerRandom(skillIdx)) {
				useble = true;
			} else {
				return false;
			}
		}
		return useble;
	}

	private boolean magicAttack(final int idx) {
		final L1SkillUse skillUse = new L1SkillUse();
		final int skillid = this.getMobSkillTemplate().getSkillId(idx);
		boolean canUseSkill = false;


		//TODO 魔封狀態判斷 by 阿傑的1098原碼
		if (skillid > 0 && !this._attacker.hasSkillEffect(64)) {
			//TODO 魔封狀態判斷 by 阿傑的1098原碼
		//	skill.setRanged(getMobSkillTemplate().getRange(idx)); // 變更技能施放距離
			canUseSkill = skillUse.checkUseSkill(null, skillid,this._target.getId(), this._target.getX(), this._target.getY(), null, 0,L1SkillUse.TYPE_NORMAL, this._attacker);
		}

		if (canUseSkill == true) {
			if (this.getMobSkillTemplate().getLeverage(idx) > 0) {
				skillUse.setLeverage(this.getMobSkillTemplate().getLeverage(idx));
			}
			skillUse.handleCommands(null, skillid, this._target.getId(),this._target.getX(), this._target.getX(), null, 0,L1SkillUse.TYPE_NORMAL, this._attacker);
			// 使用スキルによるsleepTimeの設定
			final L1Skills skill = SkillsTable.getInstance().getTemplate(skillid);
			if (skill.getTarget().equals("attack") && skillid != 18) { // 有方向魔法
				this._sleepTime = this._attacker.getNpcTemplate().getAtkMagicSpeed();
			} else { // 無方向魔法
				this._sleepTime = this._attacker.getNpcTemplate().getSubMagicSpeed();
			}

			return true;
		}
		return false;
	}

	private void mobspawn(final int summonId) {
		try {
			final L1Npc spawnmonster = NpcTable.getInstance().getTemplate(summonId);
			if (spawnmonster != null) {
				L1NpcInstance mob = null;
				try {
					final String implementationName = spawnmonster.getImpl();
					final Constructor<?> _constructor = Class.forName(
							new StringBuilder()
							.append("l1j.server.server.model.Instance.")
							.append(implementationName)
							.append("Instance").toString())
							.getConstructors()[0];
					mob = (L1NpcInstance) _constructor
					.newInstance(new Object[] { spawnmonster });
					mob.setId(IdFactory.getInstance().nextId());
					final L1Location loc = this._attacker.getLocation().randomLocation(8,
							false);
					final int heading = Random.nextInt(8);
					mob.setX(loc.getX());
					mob.setY(loc.getY());
					mob.setHomeX(loc.getX());
					mob.setHomeY(loc.getY());
					final short mapid = this._attacker.getMapId();
					mob.setMap(mapid);
					mob.setHeading(heading);
					L1World.getInstance().storeObject(mob);
					L1World.getInstance().addVisibleObject(mob);
					final L1Object object = L1World.getInstance().findObject(mob.getId());
					final L1MonsterInstance newnpc = (L1MonsterInstance) object;
					newnpc.set_storeDroped(true); // 召喚されたモンスターはドロップ無し
					if (summonId == 45061 // カーズドスパルトイ
							|| summonId == 45161 // スパルトイ
							|| summonId == 45181 // スパルトイ
							|| summonId == 45455) { // デッドリースパルトイ
						newnpc.broadcastPacketAll(new S_DoActionGFX(newnpc.getId(), ActionCodes.ACTION_Hide));
						newnpc.setStatus(13);
						newnpc.broadcastPacketAll(new S_NPCPack(newnpc));
						newnpc.broadcastPacketAll(new S_DoActionGFX(newnpc.getId(), ActionCodes.ACTION_Appear));
						newnpc.setStatus(0);
						newnpc.broadcastPacketAll(new S_NPCPack(newnpc));
					}
					newnpc.onNpcAI();
					newnpc.turnOnOffLight();
					newnpc.startChat(L1NpcInstance.CHAT_TIMING_APPEARANCE); // チャット開始
				} catch (final Exception e) {
					_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
				}
			}
		} catch (final Exception e) {
			_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
		}
	}

	private void mobspawn(final int summonId,final  int count) {
		int i;

		for (i = 0; i < count; i++) {
			this.mobspawn(summonId);
		}
	}

	/*
	 * 物理攻撃
	 */
	private boolean physicalAttack(final int idx) {
		final Map<Integer, Integer> targetList = new ConcurrentHashMap<Integer, Integer>();
		final int areaWidth = this.getMobSkillTemplate().getAreaWidth(idx);
		final int areaHeight = this.getMobSkillTemplate().getAreaHeight(idx);
		final int range = this.getMobSkillTemplate().getRange(idx);
		final int actId = this.getMobSkillTemplate().getActid(idx);
		final int gfxId = this.getMobSkillTemplate().getGfxid(idx);

		// レンジ外
		if (this._attacker.getLocation().getTileLineDistance(this._target.getLocation()) > range) {
			return false;
		}

		// 障害物がある場合攻撃不可能
		if (!this._attacker.glanceCheck(this._target.getX(), this._target.getY())) {
			return false;
		}

		this._attacker.setHeading(this._attacker.targetDirection(this._target.getX(),this._target.getY())); // 向きのセット

		if (areaHeight > 0) {
			// 範囲攻撃
			final ArrayList<L1Object> objs = L1World.getInstance().getVisibleBoxObjects(this._attacker, this._attacker.getHeading(),areaWidth, areaHeight);

			for (final L1Object obj : objs) {
				if (!(obj instanceof L1Character)) { // ターゲットがキャラクター以外の場合何もしない。
					continue;
				}

				final L1Character cha = (L1Character) obj;
				if (cha.isDead()) { // 死んでるキャラクターは対象外
					continue;
				}

				// ゴースト状態は対象外
				if (cha instanceof L1PcInstance) {
					if (((L1PcInstance) cha).isGhost()) {
						continue;
					}
				}

				// 障害物がある場合は対象外
				if (!this._attacker.glanceCheck(cha.getX(), cha.getY())) {
					continue;
				}

				if (this._target instanceof L1PcInstance
						|| this._target instanceof L1SummonInstance
						|| this._target instanceof L1PetInstance) {
					// 対PC
					if (obj instanceof L1PcInstance
							&& !((L1PcInstance) obj).isGhost()
							&& !((L1PcInstance) obj).isGmInvis()
							|| obj instanceof L1SummonInstance
							|| obj instanceof L1PetInstance) {
						targetList.put(obj.getId(), 0);
					}
				} else {
					// 対NPC
					if (obj instanceof L1MonsterInstance) {
						targetList.put(obj.getId(), 0);
					}
				}
			}
		} else {
			// 単体攻撃
			targetList.put(this._target.getId(), 0); // ターゲットのみ追加
		}

		if (targetList.size() == 0) {
			return false;
		}

		final Iterator<Integer> ite = targetList.keySet().iterator();
		while (ite.hasNext()) {
			final int targetId = ite.next();
			final L1Object object = L1World.getInstance().findObject(targetId);
			if(object instanceof L1ItemInstance){return false;}
			final L1AttackModel attack = new L1AttackNpc(this._attacker, (L1Character) object);
			if (attack.calcHit()) {
				if (this.getMobSkillTemplate().getLeverage(idx) > 0) {
					attack.setLeverage(this.getMobSkillTemplate().getLeverage(idx));
				}
				attack.calcDamage();
			}
			if (actId > 0) {
				attack.setActId(actId);
			}
			// 攻撃モーションは実際のターゲットに対してのみ行う
			if (targetId == this._target.getId()) {
				if (gfxId > 0) {
					this._attacker.broadcastPacketAll(new S_SkillSound(this._attacker.getId(), gfxId));
				}
				attack.action();
			}
			attack.commit();
		}

		this._sleepTime = this._attacker.getAtkspeed();
		return true;
	}

	/*
	 * 15セル以内で射線が通るPCを指定したモンスターに強制変身させる。 対PCしか使えない。
	 */
	private boolean poly(final int idx) {
		final int polyId = this.getMobSkillTemplate().getPolyId(idx);
		boolean usePoly = false;

		if (polyId == 0) {
			return false;
		}

		for (final L1PcInstance pc : L1World.getInstance().getVisiblePlayer(this._attacker)) {
			if (pc.isDead()) { // 死亡している
				continue;
			}
			if (pc.isGhost()) {
				continue;
			}
			if (pc.isGmInvis()) {
				continue;
			}
			if (this._attacker.glanceCheck(pc.getX(), pc.getY()) == false) {
				continue; // 射線が通らない
			}

			final int npcId = this._attacker.getNpcTemplate().get_npcId();
			switch (npcId) {
			case 81082: // ヤヒの場合
				pc.getInventory().takeoffEquip(945); // 牛のpolyIdで装備を全部外す。
				break;
			default:
				break;
			}
			L1PolyMorph.doPoly(pc, polyId, 1800, L1PolyMorph.MORPH_BY_NPC);

			usePoly = true;
		}
		if (usePoly) {

			// 変身させた場合、オレンジの柱を表示する。
			for (final L1PcInstance pc : L1World.getInstance().getVisiblePlayer(this._attacker)) {
				final S_SkillSound skill = new S_SkillSound(pc.getId(), 230);
				pc.sendPackets(skill);
				pc.broadcastPacketAll(skill);
				break;
			}
			// 魔法を使う動作のエフェクト
			final S_DoActionGFX gfx = new S_DoActionGFX(this._attacker.getId(),ActionCodes.ACTION_SkillBuff);
			this._attacker.broadcastPacketAll(gfx);

			this._sleepTime = this._attacker.getNpcTemplate().getSubMagicSpeed();
		}

		return usePoly;
	}

	public void resetAllSkillUseCount() {
		if (this.getMobSkillTemplate() == null) {
			return;
		}

		for (int i = 0; i < this.getMobSkillTemplate().getSkillSize(); i++) {
			this._skillUseCount[i] = 0;
		}
	}

	private L1NpcInstance searchMinCompanionHp() {
		L1NpcInstance npc;
		L1NpcInstance minHpNpc = null;
		int hpRatio = 100;
		int companionHpRatio;
		final int family = this._attacker.getNpcTemplate().get_family();

		for (final L1Object object : L1World.getInstance().getVisibleObjects(this._attacker)) {
			if (object instanceof L1NpcInstance) {
				npc = (L1NpcInstance) object;
				if (npc.getNpcTemplate().get_family() == family) {
					companionHpRatio = npc.getCurrentHp() * 100/ npc.getMaxHp();
					if (companionHpRatio < hpRatio) {
						hpRatio = companionHpRatio;
						minHpNpc = npc;
					}
				}
			}
		}
		return minHpNpc;
	}

	public void setSleepTime(final int i) {
		this._sleepTime = i;
	}

	/*
	 * スキル攻撃 スキル攻撃可能ならばtrueを返す。 攻撃できなければfalseを返す。
	 */
	public boolean skillUse(final L1Character tg,final  boolean isTriRnd) {
		if (this._mobSkillTemplate == null) {
			return false;
		}
		this._target = tg;

		int type;
		type = this.getMobSkillTemplate().getType(0);

		if (type == L1MobSkill.TYPE_NONE) {
			return false;
		}

		int[] skills = null;
		int skillSizeCounter = 0;
		final int skillSize = this.getMobSkillTemplate().getSkillSize();
		if (skillSize >= 0) {
			skills = new int[skillSize];
		}

		int i = 0;
		for (i = 0; i < this.getMobSkillTemplate().getSkillSize()
		&& this.getMobSkillTemplate().getType(i) != L1MobSkill.TYPE_NONE; i++) {

			// changeTargetが設定されている場合、ターゲットの入れ替え
			final int changeType = this.getMobSkillTemplate().getChangeTarget(i);
			if (changeType > 0) {
				this._target = this.changeTarget(changeType, i);
			} else {
				// 設定されてない場合は本来のターゲットにする
				this._target = tg;
			}

			if (this.isSkillUseble(i, isTriRnd) == false) {
				continue;
			} else { // 条件にあうスキルが存在する
				skills[skillSizeCounter] = i;
				skillSizeCounter++;
			}
		}

		if (skillSizeCounter != 0) {
			final int num = Random.nextInt(skillSizeCounter);
			if (this.useSkill(skills[num])) { // スキル使用
				return true;
			}
		}

		return false;
	}

	private void skillUseCountUp(final int idx) {
		this._skillUseCount[idx]++;
	}

	private boolean summon(final int idx) {
		final int summonId = this.getMobSkillTemplate().getSummon(idx);
		final int min = this.getMobSkillTemplate().getSummonMin(idx);
		final int max = this.getMobSkillTemplate().getSummonMax(idx);
		int count = 0;

		if (summonId == 0) {
			return false;
		}

		count = Random.nextInt(max) + min;
		this.mobspawn(summonId, count);

		// 魔方陣の表示
		this._attacker.broadcastPacketAll(new S_SkillSound(this._attacker.getId(), 761));

		// 魔法を使う動作のエフェクト
		final S_DoActionGFX gfx = new S_DoActionGFX(this._attacker.getId(),ActionCodes.ACTION_SkillBuff);
		this._attacker.broadcastPacketAll(gfx);

		this._sleepTime = this._attacker.getNpcTemplate().getSubMagicSpeed();
		return true;
	}

	private boolean useSkill(final int i) {
		boolean isUseSkill = false;
		final int type = this.getMobSkillTemplate().getType(i);
		if (type == L1MobSkill.TYPE_PHYSICAL_ATTACK) { // 物理攻撃
			if (this.physicalAttack(i) == true) {
				this.skillUseCountUp(i);
				isUseSkill = true;
			}
		} else if (type == L1MobSkill.TYPE_MAGIC_ATTACK) { // 魔法攻撃
			if (this.magicAttack(i) == true) {
				this.skillUseCountUp(i);
				isUseSkill = true;
			}
		} else if (type == L1MobSkill.TYPE_SUMMON) { // サモンする
			if (this.summon(i) == true) {
				this.skillUseCountUp(i);
				isUseSkill = true;
			}
		} else if (type == L1MobSkill.TYPE_POLY) { // 強制変身させる
			if (this.poly(i) == true) {
				this.skillUseCountUp(i);
				isUseSkill = true;
			}
		}
		return isUseSkill;
	}
}
